﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using OWLServer.owl.tipos;
using System.Xml.Linq;
using OWLServer.owl.etiquetas;

namespace OWLServer.owl.utilidades
{
    /// <summary>
    /// Clase que contiene métodos para manejar los individuos de una ontología.
    /// </summary>
    class UtilidadesIndividuos
    {
        /// <summary>
        /// Construye los Individuos del OWL
        /// </summary>
        /// <param name="ontologia">Ontología</param>
        /// <param name="root">Raiz del documento OWL</param>
        public static void construirIndividuos(Ontologia ontologia, XElement root)
        {
            if (UtilidadesEspaciosNombres.contieneOWL(ontologia.getEspaciosNombresDictiorary()) && UtilidadesEspaciosNombres.contieneRDF(ontologia.getEspaciosNombresDictiorary()))
            {
                IEnumerable<XElement> eleIndividuos;
                Individuo individuo;
                Problema problema;
                IList<Clase> listaClases;

                // Lista de individuos encontrados
                List<Individuo> individuos = new List<Individuo>();

                Clase clase;
                int sizeClases = ontologia.Clases.Count;

                // Recorremos la clases para recuperar sus individuos
                for (int i = 0; i < sizeClases; i++)
                {
                    clase = ontologia.Clases[i];

                    // Extraemos los elementos de los individuos de la clase
                    eleIndividuos =
                        from el in root.Elements(UtilidadesEspaciosNombres.getEspacioNombres(ontologia.getEspaciosNombresDictiorary()[clase.EspacioNombres.ToString()]) + clase.Id)
                        select el;

                    // Creamos los objetos Individuo a partir de los elementos recuperados
                    foreach (XElement eleIndividuo in eleIndividuos)
                    {
                        individuo = new Individuo(UtilidadesElementos.getElemento(UtilidadesElementos.getId(eleIndividuo, ontologia.getEspaciosNombresDictiorary()), ontologia));

                        // Si no lo contiene ya lo agregamos
                        if (!ontologia.Individuos.Contains(individuo))
                        {
                            // Obtenemos la clase de la que es el individuo
                            individuo.Tipos.Add(clase);

                            // Extraemos, el resto, tipos de clases de las que es el individuo
                            extraerTiposIndividuo(ontologia, eleIndividuo, individuo);

                            individuo.ElementoOWL = eleIndividuo; // Elemento que representa al individuo en el OWL
                            individuos.Add(individuo);
                            ontologia.Individuos.Add(individuo);
                        }
                        else // Si ya lo contenia, agregamos un problema
                        {
                            problema = new Problema(TipoProblema.INDIVIDUO_DUPLICADO, individuo);
                            ontologia.Problemas.Add(problema);
                        }
                    }
                }

                // Volvemos al recorrer los individuos para ver las relaciones de unos con otros
                foreach (Individuo indiv in individuos)
                {
                    // Estraemos las propiedades del individuo
                    listaClases = UtilidadesClases.obtenerPadresYEquivalentes(indiv.Tipos);
                    extrarPropiedadesDato(indiv, ontologia, indiv.ElementoOWL, listaClases);
                    extrarPropiedadesObjeto(indiv, ontologia, indiv.ElementoOWL, listaClases);

                    // Comprobamos si es igual a algun individuo
                    extraerIndividuosIguales(ontologia, indiv);
                }

                // Vemos los Individuos que son distintos unos con otros
                estraerIndividuosDiferentes(ontologia, root);

                // Vemos las propiedades negadas que hay
                estraerPropiedadesNegadas(ontologia, root);
            }
        }

        /// <summary>
        /// Extrae los tipos de clase que es el individuo
        /// </summary>
        /// <param name="ontologia">Ontología a la que pertenece</param>
        /// <param name="eleIndividuo">Elemento OWL que representa al individuo</param>
        /// <param name="individuo">Individuo a analizar</param>
        private static void extraerTiposIndividuo(Ontologia ontologia, XElement eleIndividuo, Individuo individuo)
        {
            Elemento recurso;

            IEnumerable<XElement> eleTiposClases =
                                from el in eleIndividuo.Elements(UtilidadesEspaciosNombres.getEspacioNombres(ontologia.getEspaciosNombresDictiorary()[EtiquetasRDF.NOMBRE]) + EtiquetasRDF.TIPO)
                                select el;

            foreach (XElement eleTipo in eleTiposClases)
            {
                recurso = UtilidadesElementos.getElemento(UtilidadesElementos.getNombreRecurso(eleTipo, ontologia.getEspaciosNombresDictiorary()), ontologia);
                if (null == recurso) // El tipo esta definido por una restriccion
                {
                    UtilidadesRestricciones.construirRestriccionesIndividuo(ontologia, eleTipo, individuo);
                }
                else // Es de un tipo normal. Es de una clase
                {
                    individuo.Tipos.Add(UtilidadesClases.getClase(ontologia, recurso));
                }
            }
        }

        /// <summary>
        /// Extrae los individuos iguales al individuo que le pasemos
        /// </summary>
        /// <param name="ontologia">Ontología a la que pertenece</param>
        /// <param name="eleIndividuo">Elemento OWL que representa al individuo</param>
        /// <param name="individuo">Individuo a analizar</param>
        private static void extraerIndividuosIguales(Ontologia ontologia, Individuo individuo)
        {
            Individuo individuoRecurso;
            Elemento recurso;

            IEnumerable<XElement> eleIguales =
                        from el in individuo.ElementoOWL.Elements(UtilidadesEspaciosNombres.getEspacioNombres(ontologia.getEspaciosNombresDictiorary()[EtiquetasOWL.NOMBRE]) + EtiquetasOWL.IGUAL_A)
                        select el;

            foreach (XElement eleIgual in eleIguales)
            {
                recurso = UtilidadesElementos.getElemento(UtilidadesElementos.getNombreRecurso(eleIgual, ontologia.getEspaciosNombresDictiorary()), ontologia);
                individuoRecurso = getIndividuo(ontologia, recurso);

                individuo.Iguales.Add(individuoRecurso);
                individuoRecurso.Iguales.Add(individuo);
            }
        }

        /// <summary>
        /// Extrae las propiedades de tipo dato que tiene el individuo
        /// </summary>
        /// <param name="individuo">Individuo a analizar</param>
        /// <param name="ontologia">Ontología a la que pertenece</param>
        /// <param name="eleIndividuo">Elemento OWL que representa al individuo</param>
        /// <param name="listaClases">Lista de clases para obtener las propiedades (segun el dominio)</param>
        private static void extrarPropiedadesDato(Individuo individuo, Ontologia ontologia, XElement eleIndividuo, IList<Clase> listaClases)
        {
            List<Propiedad> listaPropiedades = UtilidadesPropiedades.obtenerPropiedades(ontologia.PropiedadesDatos, listaClases);
            IEnumerable<XElement> elePropiedades;

            foreach(Propiedad propiedad in listaPropiedades)
            {
                // Extraemos los elementos de los individuos de la clase
                elePropiedades =
                    from el in eleIndividuo.Elements(UtilidadesEspaciosNombres.getEspacioNombres(ontologia.getEspaciosNombresDictiorary()[propiedad.EspacioNombres.ToString()]) + propiedad.Id)
                    select el;

                // Recorremos las propiedades de tipo dato del individuo
                foreach (XElement elePropiedad in elePropiedades)
                {
                    individuo.PropiedadesDatos.Add(new PropiedadDatoIndividuo(elePropiedad.Value, propiedad));
                }
            }
        }

        /// <summary>
        /// Extrae las propiedades de tipo objeto que tiene el individuo
        /// </summary>
        /// <param name="individuo">Individuo a analizar</param>
        /// <param name="ontologia">Ontología a la que pertenece</param>
        /// <param name="eleIndividuo">Elemento OWL que representa al individuo</param>
        /// <param name="listaClases">Lista de clases para obtener las propiedades (segun el dominio)</param>
        private static void extrarPropiedadesObjeto(Individuo individuo, Ontologia ontologia, XElement eleIndividuo, IList<Clase> listaClases)
        {
            IEnumerable<XElement> elePropiedades;
            Elemento recurso;
            Individuo individuoRango;

            List<Propiedad> listaPropiedades = UtilidadesPropiedades.obtenerPropiedades(ontologia.PropiedadesObjetos, listaClases);
            foreach (Propiedad propiedad in listaPropiedades)
            {
                // Extraemos los elementos de los individuos de la clase
                elePropiedades =
                    from el in eleIndividuo.Elements(UtilidadesEspaciosNombres.getEspacioNombres(ontologia.getEspaciosNombresDictiorary()[propiedad.EspacioNombres.ToString()]) + propiedad.Id)
                    select el;

                // Recorremos las propiedades que tiene el Individuo
                foreach (XElement elePropiedad in elePropiedades)
                {
                    recurso = UtilidadesElementos.getElemento(UtilidadesElementos.getNombreRecurso(elePropiedad, ontologia.getEspaciosNombresDictiorary()), ontologia);
                    individuoRango = getIndividuo(ontologia, recurso);
                    // Vemos si el individuo pertenece al rango de la propiedad o si la propiedad no tienen restrinción en su rando
                    if (esIndividuoTipos(individuoRango, UtilidadesClases.obtenerHijosYEquivalentes(propiedad.Rangos)) || propiedad.Rangos.Count == 0)
                    {
                        individuo.PropiedadesObjetos.Add(new PropiedadObjetoIndividuo(individuoRango, propiedad as PropiedadObjeto));
                    }
                    else
                    {
                        ontologia.Problemas.Add(new Problema(individuo, individuoRango, TipoProblema.PROPIEDAD_FUERA_RANGO, propiedad));
                    }
                }
            }
        }

        /// <summary>
        /// Nos dice si el individuo es de alguna de las clases que le pasamos
        /// </summary>
        /// <param name="individuo">Individuo a comprobar</param>
        /// <param name="clases">Lista de clases de las que puede ser el individuo</param>
        /// <returns>Si es de algun tipo de clase o no</returns>
        private static bool esIndividuoTipos(Individuo individuo, IList<Clase> clases)
        {
            bool es = false;

            IList<Clase> tiposIndividuo = UtilidadesClases.obtenerPadresYEquivalentes(individuo.Tipos);

            for (int i = 0; i < tiposIndividuo.Count && !es; i++)
            {
                es = clases.Contains(tiposIndividuo[i]);
            }

            return es;
        }

        /// <summary>
        /// Extrae las propiedades negadas
        /// </summary>
        /// <param name="ontologia">Ontolología con la que estamos tratando</param>
        /// <param name="root">Documento OWL de la ontología</param>
        private static void estraerPropiedadesNegadas(Ontologia ontologia, XElement root)
        {
            bool esPropiedadNegada = false;
            string objetivoDato;
            Individuo individuo;
            Individuo objetivoIndividuo;
            Propiedad propiedad = null;
            Elemento recurso;
            IEnumerable<XElement> elePropiedad;
            IEnumerable<XElement> eleObjetivoDato;
            IEnumerable<XElement> eleObjetivoIndividuo;
            IEnumerable<XElement> eleIndividuo;

            // Recorremos als descripciones que hay
            IEnumerable<XElement> eleDescripciones =
                from el in root.Elements(UtilidadesEspaciosNombres.getEspacioNombres(ontologia.getEspaciosNombresDictiorary()[EtiquetasRDF.NOMBRE]) + EtiquetasRDF.DESCRIPCION)
                select el;

            // Recorremos las Descripciones
            foreach (XElement eleDescripcion in eleDescripciones)
            {
                esPropiedadNegada = UtilidadesElementos.isElementoDescripcionTipo(eleDescripcion, EtiquetasOWL.PROPIEDAD_INDIVIDUO_NEGADA, ontologia);

                // Si la propiedad es negada extraemos la información de la propiedad
                if (esPropiedadNegada)
                {
                    objetivoDato = null;
                    objetivoIndividuo = null;
                    propiedad = null;
                    individuo = null;

                    // Miramos si el objetivo es un valor, por lo que la propiedad sera una Propiedad Dato
                    eleObjetivoDato =
                    from el in eleDescripcion.Elements(UtilidadesEspaciosNombres.getEspacioNombres(ontologia.getEspaciosNombresDictiorary()[EtiquetasOWL.NOMBRE]) + EtiquetasOWL.OBJETIVO_VALOR)
                    select el;

                    foreach (XElement eleObjDat in eleObjetivoDato)
                    {
                        objetivoDato = eleObjDat.Value;
                    }

                    if (objetivoDato == null)
                    {
                        // Miramos si el objetivo es un individuo, por lo que la propiedad sera una Propiedad Dato
                        eleObjetivoIndividuo =
                        from el in eleDescripcion.Elements(UtilidadesEspaciosNombres.getEspacioNombres(ontologia.getEspaciosNombresDictiorary()[EtiquetasOWL.NOMBRE]) + EtiquetasOWL.OBJETIVO_INDIVIDUO)
                        select el;

                        foreach (XElement eleObjInd in eleObjetivoIndividuo)
                        {
                            recurso = UtilidadesElementos.getElemento(UtilidadesElementos.getNombreRecurso(eleObjInd, ontologia.getEspaciosNombresDictiorary()), ontologia);
                            objetivoIndividuo = getIndividuo(ontologia, recurso);
                        }
                    }

                    // Obtenemos la propiedad
                    elePropiedad =
                        from el in eleDescripcion.Elements(UtilidadesEspaciosNombres.getEspacioNombres(ontologia.getEspaciosNombresDictiorary()[EtiquetasOWL.NOMBRE]) + EtiquetasOWL.PROPIEDAD_NEGADA)
                        select el;

                    foreach (XElement eleProp in elePropiedad)
                    {
                        recurso = UtilidadesElementos.getElemento(UtilidadesElementos.getNombreRecurso(eleProp, ontologia.getEspaciosNombresDictiorary()), ontologia);
                        if (objetivoDato == null) // Si es una propiedad de Objeto o individuo
                        {
                            propiedad = UtilidadesPropiedades.getPropiedadObjeto(ontologia, recurso);
                        }
                        else // Si no, es una propiedad de Valor
                        {
                            propiedad = UtilidadesPropiedades.getPropiedadDato(ontologia, recurso);
                        }
                    }

                    // Obtenemos el individuo que tiene la propiedad negada
                    eleIndividuo =
                    from el in eleDescripcion.Elements(UtilidadesEspaciosNombres.getEspacioNombres(ontologia.getEspaciosNombresDictiorary()[EtiquetasOWL.NOMBRE]) + EtiquetasOWL.FUENTE_INDIVIDUO)
                    select el;

                    foreach (XElement eleIndiv in eleIndividuo)
                    {
                        recurso = UtilidadesElementos.getElemento(UtilidadesElementos.getNombreRecurso(eleIndiv, ontologia.getEspaciosNombresDictiorary()), ontologia);
                        individuo = getIndividuo(ontologia, recurso);
                    }

                    // Insertamos la propiedad en el individuo
                    if (individuo != null && propiedad != null)
                    {
                        if (propiedad is PropiedadObjeto) // Si es una propiedad de tipo Objeto
                        {
                            individuo.PropiedadesObjetos.Add(new PropiedadObjetoIndividuo(objetivoIndividuo, propiedad as PropiedadObjeto, true));
                        }
                        else // Si no, es una propiedad de tipo Dato
                        {
                            individuo.PropiedadesDatos.Add(new PropiedadDatoIndividuo(objetivoDato, propiedad, true));
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Extrae los individuos que son diferentes unos con otros
        /// </summary>
        /// <param name="ontologia">Ontolología con la que estamos tratando</param>
        /// <param name="root">Documento OWL de la ontología</param>
        private static void estraerIndividuosDiferentes(Ontologia ontologia, XElement root)
        {
            bool hayDistintos = false;
            IEnumerable<XElement> eleDistintos;
            IEnumerable<XElement> eleDistinto;
            List<Individuo> listaDistintos;
            Elemento elemento;

            // Recorremos als descripciones que hay
            IEnumerable<XElement> eleDescripciones =
                from el in root.Elements(UtilidadesEspaciosNombres.getEspacioNombres(ontologia.getEspaciosNombresDictiorary()[EtiquetasRDF.NOMBRE]) + EtiquetasRDF.DESCRIPCION)
                select el;

            // Recorremos las Descripciones
            foreach (XElement eleDescripcion in eleDescripciones)
            {
                hayDistintos = UtilidadesElementos.isElementoDescripcionTipo(eleDescripcion, EtiquetasOWL.TODOS_DISTINTOS, ontologia);

                // Si es de tipo distinto extraemos la coleccion
                if (hayDistintos)
                {
                    eleDistintos =
                    from el in eleDescripcion.Elements(UtilidadesEspaciosNombres.getEspacioNombres(ontologia.getEspaciosNombresDictiorary()[EtiquetasOWL.NOMBRE]) + EtiquetasOWL.MIEMBROS_DISTINTOS)
                    select el;

                    /// Suponemos que es una colección por eso no se comprueba el atributo rdf:parseType="Collection"

                    eleDistinto =
                    from el in eleDistintos.Elements(UtilidadesEspaciosNombres.getEspacioNombres(ontologia.getEspaciosNombresDictiorary()[EtiquetasRDF.NOMBRE]) + EtiquetasRDF.DESCRIPCION)
                    select el;

                    listaDistintos = new List<Individuo>();
                    // Obtenemos la lista de elementos que son distintos unos con otros
                    foreach(XElement eleDist in eleDistinto)
                    {
                        elemento = UtilidadesElementos.getElemento(UtilidadesElementos.getId(eleDist, ontologia.getEspaciosNombresDictiorary()), ontologia);

                        listaDistintos.Add(getIndividuo(ontologia, elemento));
                    }
                    // Vinculamos los individuos con sus distintos
                    foreach (Individuo distinto in listaDistintos)
                    {
                        foreach (Individuo distintoA in listaDistintos)
                        {
                            // Si no es el mismo y si no lo tiene ya agregado
                            if (!distinto.Equals(distintoA) && !distinto.Diferentes.Contains(distintoA))
                            {
                                distinto.Diferentes.Add(distintoA);
                            }
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Nos devuelve el individuo que corresponde el recurso. Si no existe la propiedad la agregamos
        /// </summary>
        /// <param name="ontologia">Ontología que estamos manejando</param>
        /// <param name="recurso">Recurso que representa la propiedad</param>
        /// <returns>Individuo que representa el recurso</returns>
        public static Individuo getIndividuo(Ontologia ontologia, Elemento recurso)
        {
            IDictionary<string, Individuo> dicIndividuos = ontologia.getIndividuosDictiorary();
            Individuo individuo = null;

            if (dicIndividuos.ContainsKey(recurso.ToString()))
            {
                individuo = dicIndividuos[recurso.ToString()];
            }
            else
            {
                individuo = new Individuo(recurso);
                ontologia.Individuos.Add(individuo);
            }

            return individuo;
        }
    }
}